package elasticsearch;

import com.alibaba.fastjson.JSON;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;

/**
 * ES客户端，增删查改:https://www.cnblogs.com/ginb/p/8716485.html<br/>
 * Created on : 2019-07-01 20:17
 * @author lizebin
 */
public class ElasticSearchDemo {

    private static Logger logger = LoggerFactory.getLogger(ElasticSearchDemo.class);

    public static final String INDEX_NAME = "bookdb_index";

    public static final String TYPE_NAME = "book";

    public static final RestHighLevelClient CLIENT = new RestHighLevelClient(
        RestClient.builder(
            new HttpHost("192.168.56.104", 9200, "http")));

    public static void main(String[] args) throws IOException {
        demo1();
        demo2();
        demo3();
        demo4();
        demo5();
        demo6();
        demo7();
        demo8();
        demo9();
        demo10();
        demo11();
        CLIENT.close();
    }

    /**
     * 查询任一字段包含 Guide 的记录
     */
    public static void demo1() throws IOException {
        //查询所有字段，包含"guide"的条目
        /*
        GET /bookdb_index/book/_search
        {
            "query": {
                "query_string": {
                    "query": "guide",
                    "fields": [ ],
                    "type": "best_fields",
                    "default_operator": "or",
                    "max_determinized_states": 10000,
                    "enable_position_increments": true,
                    "fuzziness": "AUTO",
                    "fuzzy_prefix_length": 0,
                    "fuzzy_max_expansions": 50,
                    "phrase_slop": 0,
                    "escape": false,
                    "auto_generate_synonyms_phrase_query": true,
                    "fuzzy_transpositions": true,
                    "boost": 1
                }
            }
        }
         */
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(new QueryStringQueryBuilder("guide"));
        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());

        //查询 title 字段中包含 in Action 的书
        request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(new QueryStringQueryBuilder("in Action").field("title"));
        System.out.println(searchSourceBuilder);

        //只返回特定字段
        String[] includes = new String[]{"title"};
        String[] excludes = Strings.EMPTY_ARRAY;
        searchSourceBuilder.fetchSource(includes, excludes);

        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());

        //返回指定条数
        searchSourceBuilder.from(0).size(1);
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 多字段查询
     * POST /bookdb_index/book/_search
     * {
     *     "query": {
     *         "multi_match" : {
     *             "query" : "elasticsearch guide",
     *             "fields": ["title^1.0", "summary^1.0"]
     *         }
     *     }
     * }
     * @throws Exception
     */
    public static void demo2() throws IOException {

        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //多字段查询
        searchSourceBuilder.query(new QueryStringQueryBuilder("elasticsearch guide").field("title").field("summary"));
        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());

    }

    /**
     * 提高某个字段的分值，多字段查询
     * @throws IOException
     */
    public static void demo3() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //提高某一个字段的分数
        searchSourceBuilder.query(new QueryStringQueryBuilder("elasticsearch guide").field("title").field("summary", 3.0f));
        String[] includes = new String[]{"title", "summary", "public_date"};
        String[] excludes = Strings.EMPTY_ARRAY;
        searchSourceBuilder.fetchSource(includes, excludes);
        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * must 等同于 AND
     * must_not 等同于 NOT
     * should 等同于 OR
     * @throws IOException
     */
    public static void demo4() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
            .should(QueryBuilders.multiMatchQuery("Elasticsearch Solr", "title"))
            .must(QueryBuilders.matchQuery("authors", "\"clinton gormley"))
            .mustNot(QueryBuilders.matchQuery("authors", "radu gheorge"));
        searchSourceBuilder.query(boolQueryBuilder);

        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 模糊查询
     * 搜索的时候，可能输入的搜索文本会出现误拼写的情况:
     * doc1: hello world
     * doc2: hello java
     * 搜索：hallo , 这是搜索不到的，那么有没有办法做好可以搜索了，就要用到fuzzy了
     * fuzzy搜索技术 –> 自动将拼写错误的搜索文本，进行纠正，纠正以后去尝试匹配索引中的数据
     * @throws IOException
     */
    public static void demo5() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MultiMatchQueryBuilder multiQueryBuilder = QueryBuilders.multiMatchQuery("comprihensiv guide", "title", "summary").fuzziness("AUTO");
        searchSourceBuilder.query(multiQueryBuilder);

        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 通配符查询
     * @throws IOException
     */
    public static void demo6() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("authors", "t*");
        searchSourceBuilder.query(wildcardQueryBuilder);

        //设置高亮
        String preTags = "<strong>";
        String postTags = "</strong>";
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(preTags);//设置前缀
        highlightBuilder.postTags(postTags);//设置后缀
        highlightBuilder.field("authors");//设置高亮字段

        searchSourceBuilder.highlighter(highlightBuilder);
        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);

        //高亮展示忒麻烦，先不解决.......
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 通配符查询
     * @throws IOException
     */
    public static void demo7() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        RegexpQueryBuilder regexpQueryBuilder = QueryBuilders.regexpQuery("authors", "t[a-z]*y");
        searchSourceBuilder.query(regexpQueryBuilder);

        //设置高亮
        String preTags = "<strong>";
        String postTags = "</strong>";
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(preTags);//设置前缀
        highlightBuilder.postTags(postTags);//设置后缀
        highlightBuilder.field("authors");//设置高亮字段

        searchSourceBuilder.highlighter(highlightBuilder);
        System.out.println(searchSourceBuilder);
        request.source(searchSourceBuilder);

        //高亮展示忒麻烦，先不解决.......
        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 短语匹配
     * @throws IOException
     */
    public static void demo8() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MultiMatchQueryBuilder multiMatchQueryBuilder = new MultiMatchQueryBuilder("search engine");
        multiMatchQueryBuilder.field("title").field("summary");
        multiMatchQueryBuilder.type(MultiMatchQueryBuilder.Type.PHRASE);
        multiMatchQueryBuilder.slop(3);

        searchSourceBuilder.query(multiMatchQueryBuilder);

        System.out.println(searchSourceBuilder.query());

        request.source(searchSourceBuilder);

        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 短语匹配
     * @throws IOException
     */
    public static void demo9() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MultiMatchQueryBuilder multiMatchQueryBuilder = new MultiMatchQueryBuilder("search eng");
        multiMatchQueryBuilder.field("title").field("summary");
        multiMatchQueryBuilder.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX);
        multiMatchQueryBuilder.slop(3);//slop 参数（用来调整单词顺序和不太严格的相对位置）
        multiMatchQueryBuilder.maxExpansions(10);//max_expansions 参数（用来限制查询项的数量，降低对资源需求的强度）

        searchSourceBuilder.query(multiMatchQueryBuilder);

        request.source(searchSourceBuilder);

        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 短语前缀匹配
     * @throws IOException
     */
    public static void demo10() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MultiMatchQueryBuilder multiMatchQueryBuilder = new MultiMatchQueryBuilder("search eng");
        multiMatchQueryBuilder.field("title").field("summary");
        multiMatchQueryBuilder.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX);
        multiMatchQueryBuilder.slop(3);//slop 参数（用来调整单词顺序和不太严格的相对位置）
        multiMatchQueryBuilder.maxExpansions(10);//max_expansions 参数（用来限制查询项的数量，降低对资源需求的强度）

        searchSourceBuilder.query(multiMatchQueryBuilder);

        request.source(searchSourceBuilder);

        print(CLIENT.search(request).getHits().getHits());
    }

    /**
     * 查询字符串，快捷方式:那些作者是 “grant ingersoll” 或 “tom morton” 的某本书当中，使用查询项 “search algorithm” 进行一次模糊查询，搜索全部字段
     *
     * 符号查询有问题
     * AND 对应: +
     * OR 对应: |
     * NOT 对应: -
     * @throws IOException
     */
    public static void demo11() throws IOException {
        SearchRequest request = new SearchRequest(INDEX_NAME);
        request.types(TYPE_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder("(summary:search~1 algorithm~1) AND (authors:grant ingersoll OR tom morton)");
        queryStringQueryBuilder.field("_all").field("summary", 2);

        searchSourceBuilder.query(queryStringQueryBuilder);

        System.out.println(searchSourceBuilder.query());

        request.source(searchSourceBuilder);

        print(CLIENT.search(request).getHits().getHits());
    }

    public static void print(SearchHit[] hits) {
        long timeStamp = Math.abs(System.nanoTime());
        System.out.println("---------------begin:result(" + timeStamp + ")---------------------");
        if (hits.length > 0) {
            List<Map<String, Object>> list = new LinkedList<>();
            Stream.of(hits).forEach(hit -> {
                //System.out.println(JSON.toJSONString(hit.getHighlightFields()));
                System.out.println(JSON.toJSONString(hit.getSourceAsMap()));
            });
        }
        System.out.println("---------------end:result(" + timeStamp + ")---------------------");
    }
}
